//namespace scope
(function( DMI, $, undefined ){
		
var MUnit = DMI.MUnit = DMI.MUnit || {};

var Format = DMI.Format;
var Utils = DMI.Utils;

var modctx = DMI.modctx;
var modconstants = DMI.modconstants;


//determines unit type sort order (and identifies summons & commanders)
var unitSortableTypes = {
	'cmdr (cap only)': 		'10.cmdr',
	'commander': 			'11.cmdr',
	'cmdr (u-water)': 		'12.cmdr',
	
	'unit (cap only)': 		'20.',
	'unit': 			'21.',
	'unit (u-water)': 		'22.',
	
	'hero (multi)': 		'30.cmdr',
	'hero (unique)': 		'31.cmdr',
	
	'cmdr (summoned)': 		'40.sum.cmdr',
	'unit (summoned)': 		'41.sum',
	'unit (created)':		'42.sum',
	
	'unit (created)':		'45.',

	'combat summon (cmdr)':	 	'50.sum.cmdr',
	'combat summon':		'51.sum',
	
	'assassin (summoned)':		'52.sum.cmdr',
	'infiltrator (indy summon)':	'53.sum.cmdr',
	
	'pretender': 			'60.cmdr',
	
	'special': 			'70.cmdr',
	'cmdr (indy summon)':		'80.sum.cmdr',
	'unit (indy summon)':		'81.sum',
	'': 				'90.cmdr' //default (notype)
}
//unit types refered to in index.html (unit type filter options) and SpellTables.js (summon referals)

function isCmdr(u) {
	return u.sorttype.indexOf('cmdr') != -1 && !u.createdby;
}
function isSummon(u) {
	return u.sorttype.indexOf('sum') != -1;
}


//////////////////////////////////////////////////////////////////////////
// PREPARE DATA
//////////////////////////////////////////////////////////////////////////

//defaults
MUnit.initUnit = function(o) {
	o.weapons = [];
	o.armor = [];
	o.randompaths = [];
	
	o.hand = '2';
	o.head = '1';
	o.body = '1';
	o.foot = '1';
	o.misc = '2';
	
	o.gcost = '10';
	o.rcost = '1';
	//o.ressize='2';
	
	o.size = '2';
	o.hp   = '10';
	o.prot = '0';
	o.mor  = '10';
	o.mr   = '10';
	o.enc  = '10';
	o.str  = '10';
	o.att  = '10';
	o.def  = '10';
	o.prec = '10';
	o.ap   = '12';
	o.mapmove = '2';
		
	o.leader = '40';
	//o.startage = '50';
	o.sprite = { unitid: o.id };
	o.descr  = { unitid: o.id };
}

MUnit.prepareData_PreMod = function() {
	for (var oi=0, o;  o= modctx.unitdata[oi];  oi++) {
		
		o.weapons = Utils.keyListToTable(o, 'wpn');
		
		//consolidate armor types to a single array
		var arr = [];
		for (var k in {armor:1, helmet:1, shield:1, msc:1}) {
			if (!o[k]) continue;
			arr.push(o[k])
			delete o[k];
		}
		o.armor = arr;	
		
		//native sprite object (may be copied to another unit)
		o.sprite = { unitid: o.id };
		o.descr  = { unitid: o.id };
		
		//random magic paths                                                     
		o.randompaths = [];
		var pmasks = DMI.modconstants[11];//{                                                                   
		// 	128:'F',  256:'A',  512:'W',  1024:'E',  2048:'S',  4096:'D',  8192:'N',  16384:'B',  32768:'H'
		// };
		for (var i=1, bf;  bf= o['mask'+i];  i++) {
			bf = parseInt(bf);
			
			var nbr = parseInt( o['nbr'+i] ) || 1;
			var chance = o['chance'+i] % 100;
			var levels = (o['chance'+i] - chance) / 100;
			
			delete o['mask'+i]; delete o['chance'+i]; delete o['nbr'+i];
			
			//turn bitfield into a string of path letters
			var pstr = '';
			for (k in pmasks) if (bf & parseInt(k)) pstr += pmasks[k];
			
			for (var j=0; j<nbr; j++) 
				o.randompaths.push({ paths: pstr,  levels: levels || '1',  chance: chance || '100'});
		}
		
		//set single path magicboost
		if (o.magicboost) {
			o['magicboost_'+o.magicboost] = o.n_magicboost;
			//delete o.magicboost;
			delete o.n_magicboost;
		}
		//set single gemprod
		if (o.gemprod) {
			o['gemprod_'+o.gemprod] = o.n_gemprod;
			//delete o.gemprod;
			delete o.n_gemprod;
		}
		//fix eyes value
		if (o.eyes)
			o.eyes = Utils.sum(o.eyes, 2);
		
		//fix summons
		if (o.makemonster) {
			o['makemonster'+o.n_makemonster] = o.makemonster;
			delete o.makemonster;
		}
		if (o.summon) {
			o['summon'+o.n_summon] = o.summon;
			delete o.summon;
		}
		if (o.domsummon) {
			//#domsummon, #domsummon2, #domsummon20
			if (o.n_domsummon != 1) {
				o['domsummon'+o.n_domsummon] = o.domsummon;
				delete o.domsummon;
			}
			delete o.n_domsummon;
		}
	}
}
	

MUnit.prepareData_PostMod = function() {
	//helpers
	for (var oi=0, o;  o= modctx.unitdata[oi];  oi++) {

		//convert to numbers (for column ordering)
		//doesn't seem to cause any further problems..
		o.id = parseFloat(o.id);
		o.type = '';
		
		o.renderOverlay = MUnit.renderOverlay;
		o.matchProperty = MUnit.matchProperty;
		
		//unique
		o.linkname = o.fullname = (o.name || '(undefined)');
		if (o.uniquename) {
			o.fullname = '“'+(o.uniquename) + '” - '+o.name;
			o.unique='1';
		}
		// if (o.subname) {
		// 	 o.linkname = o.name + ' ('+(o.subname) +')';
		// }
		
		//searchable string
		o.searchable = o.fullname.toLowerCase();
		
		//localise useful functions
		var sum = Utils.sum;
		var negative = Utils.negative;
		var mult = Utils.mult;
		var is = Utils.is;
		var normalise = Utils.normalise;
		
		//magic paths
		o.magicskill = '';
		var research = 0;
		for (var i=0; i<modconstants.pathkeys.length; i++) {
			var k = modconstants.pathkeys[i];
			var plevel  = o[k];
			
			//append to pathcost code
			if (is(plevel)) {
				o.magicskill +=  k + plevel + ' '; //string
				
				//add to research total
				if (k != 'H')
					research += parseInt(plevel);
			}
		}		
		//append random magic to pathcode
		if (o.randompaths.length) {
			//avg path bonus
			var n = 0;
			for (var i=0, r; r= o.randompaths[i]; i++)
				n += parseInt(r.levels) * parseInt(r.chance) / 100;
			
			n = Math.floor(n);
			research += n;
			
			o.magicskill += 'U' + String(n) + ' ';
		}
		
		//priests are always sacred
		if (is(o.H)) o.holy = 1;
		
		//casters have base research 2 
		if (research)
			research += 2;
		else {
			//var r = /Non magic researcher: (\d+)/.exec(o.special);
			if (is(o.nonmagicresearcher)) research = parseInt(o.nonmagicresearcher);
		}			
		//add research bonus
		if (research && is(o.researchbonus)) 
			research += parseInt(o.researchbonus);
		//append research to pathcode
		if (research) 
			o.magicskill += 'R' + String(research) + ' ';
				
		//resource costs
		var rcost = parseInt(o.rcost);
		var ressize = parseInt(o.ressize) || parseInt(o.size); //{1:0.5,  2:1,  3:1.5,  4:2,  5:2.5,  6:3}[o.ressize || '2'];

		//filter out weapons we cant find
		var weapons = [];
		for (var i=0, wid, w;  wid= o.weapons[i];  i++) {
			if (! (w= modctx.wpnlookup[wid])) {
				console.log('weapon "'+wid+'" not found (unit '+o.id+')');
				continue;	
			}
			weapons.push(w);
		}
		o.weapons = weapons;
		
		//default fist
		if (!is(o.immobile)) {
			var has_melee_wpn = false
			for (var i=0,w; w= o.weapons[i]; i++) {
				if (w.wpnclass=='melee')
					has_melee_wpn = true;
			}
			if (!has_melee_wpn)
				weapons.push(modctx.wpnlookup[92])
		}
		
		//finish weapons
		for (var i=0, w;  w= o.weapons[i];  i++) {
			//backlink on wpn
			Utils.joinArray( Utils.unitRef(o.id, o.name), w.used_by )
				
			//add resource cost to unit
			rcost += parseInt(w.rcost || '0') * ressize / 2;
		}
		
		//create a temp dict of armor by type (so only last of each type will be remembered)
		var adict = {},  armor=[];
		for (var i=0, aid, a; aid= (o.armor || [])[i]; i++) {
			if (! (a= modctx.armorlookup[aid]))  {
				console.log('armor "'+aid+'" not found (unit '+o.id+')');
				continue;
			}
			if (a.id != 0) //armor: "none"
				adict[a.type] = a;
		}
		//and... now put them back in an array
		for (var k in adict) {
			var a = adict[k];
			armor.push(a);
			
			//backlink on armor
			a.used_by.push( Utils.unitRef(o.id, o.name) );
					
			//add resource cost to unit
			rcost += parseInt(a.rcost || '0') * ressize / 2;
		}
		o.armor = armor;
		
		//gladiators
		if (parseInt(o.rcost) < 0) {
			o.special = 'Fights one battle then leaves. ' + (o.special || '');
			rcost = 1;
		}
		
		//numeric gold and resource costs (for sorting)
		o.gcost = parseInt(o.gcost || '0');
		if (!o.gcost)
			o.rcost = 0;
		else
			o.rcost = Math.floor(rcost || 1);
		
	}
}

//stuff that depends on unit type must come after parsing nation data
MUnit.prepareData_PostNationData = function(o) {
	for (var oi=0, o;  o= modctx.unitdata[oi];  oi++) {
		
		//creat nation summary
		o.nationsummary = '';
		o.numnations = 0;
		var k;
		for (k in (o.nations || {})) {
			o.numnations ++;
		}
		o.nationsummary = DMI.MNation.nationSummary(o.nations);//DMI.MNation.renderHash( o.nations, 1, false );

		//clear pretender cost
		if (o.type == 'pretender') {
			//delete o.gcost;
			delete o.rcost;
		}
		//sorttype
		o.sorttype = unitSortableTypes[o.type];

		//show magic paths on grid for commanders only
		if (isCmdr(o) && o.magicskill) 
			o.listed_mpath = '0'+o.magicskill;
		else o.listed_mpath = '';

		o.holy = o.holy || '';

		//add backlinks to units created by other units
		var summonkeys = [
			'domsummon', 'domsummon2', 'domsummon20',
			'summon1', 'summon2', 'summon3', 'summon4', 'summon5',
			'makemonster1', 'makemonster2', 'makemonster3', 'makemonster4', 'makemonster5'
		];
		for (var i=0,s; s= summonkeys[i]; i++) {
			var sumu;
			// if (o[s] && (sumu= modctx.unitlookup[o[s]])) {
			// 	if (sumu.type && sumu.type!='unit (created)') {
			// 		//find right version of this unit
			// 		var newu = modctx.getUnitOfType(sumu, 'unit (created)');
			// 		if (newu) sumu = newu;
			// 		else {
			// 			sumu = modctx.cloneUnit(sumu);
			// 			sumu.nations = {};
			// 			sumu.eracodes = {};
			// 		}
			// 	}
			// 	sumu.type = 'unit (created)';
			// 	sumu.createdby = sumu.createdby || [];
			// 	if (!Utils.inArray(o, sumu.createdby)) 
			// 		sumu.createdby.push(o);
			// }
			if (o[s] && (sumu= modctx.unitlookup[o[s]])) {
				sumu.createdby = sumu.createdby || [];
				if (!sumu.type)
					sumu.type = 'unit (created)';
				
				if (!Utils.inArray(o, sumu.createdby)) 
					sumu.createdby.push(o);
			}
		}
		
		
		o.unprep = true;   //needs final prep on render
	}
}

//preparation deferred till rendering overlay
MUnit.prepareForRender = function(o) {
	if (o.unprep) {
		delete o.unprep;   //prep just once
		
		var sum = Utils.sum;
		var negative = Utils.negative;
		var mult = Utils.mult;
		var is = Utils.is;
		var _isCmdr = isCmdr(o);
		
		//set sprite url
		if (o.sprite.spr1)
			o.sprite.url = 'mods/' + o.sprite.spr1.replace('.tga', '.png').replace(/^.\//, '')
		else
			o.sprite.url = 'images/sprites/' + Utils.paddedNum(o.sprite.unitid,4)+'_1.png';
		
		
		//local helper: apply bonus to stat and add it to tooltip
		o.titles = {};
		function bonus(reason, stat, inc) {
			inc = parseInt(inc);
			if (inc) {
				var oldv =  o[stat] || '0';
				o.titles[stat] =  o.titles[stat]  ?  o.titles[stat]+', '  :  oldv;
				o.titles[stat]+= ' '+Format.Signed(String(inc)) +' ('+reason+')';
				o[stat] = sum(oldv, inc);
			}
		}
		
		//init age
		if (o.age == '0') delete o.age;
		if (o.age == '-1') o.age = '0';
		if (o.maxage == '0') delete o.maxage;
		
		//experimental calculation for startage from maxage (roughly fitting a curve)
		function startage(maxage){
			return Math.round((maxage * 21/50) + (maxage*maxage * -0.00005) + (maxage*maxage*maxage * 0.0000000045));
		}
		// coldblooded maxage = maxage *1.5
		// animal maxage = maxage *1.5
		
		//default age
		if (is(o.inanimate)) {
			// if (!o.maxage) o.maxage = '2400'; //depends on size?
			if (!o.maxage) o.maxage = mult( 400, o.size );			
			
			//if (!o.startage) o.startage = '??';
			if (!o.startage) o.startage = String(startage(parseInt(o.maxage) || 0));
			//if (o.E) bonus('earth magic', 'maxage', mult(o.maxage, parseInt(o.E) * 0.5));
		}
		else if (is(o.undead)) {
			if (!o.maxage) o.maxage = '500';
			// if (!o.startage) o.startage = '187';
			if (!o.startage) o.startage = String(startage(parseInt(o.maxage) || 0));
			
			if (o.D && _isCmdr) bonus('death magic', 'maxage', mult(o.maxage, parseInt(o.D) * 0.5));
		}
		else if (is(o.demon)) {
			if (!o.maxage) o.maxage = '1000';
			// if (!o.startage) o.startage = '370';
			if (!o.startage) o.startage = String(startage(parseInt(o.maxage) || 0));
			
			if (o.B && _isCmdr) bonus('blood magic', 'maxage', mult(o.maxage, parseInt(o.B) * 0.5));
		}
		else {
			if (!o.maxage) o.maxage = '50';
			// if (!o.startage) o.startage = '22';
			if (!o.startage) o.startage = String(startage(parseInt(o.maxage) || 0));
			
			if (o.N && _isCmdr) bonus('nature magic', 'maxage', mult(o.maxage, parseInt(o.N) * 0.5));
			if (o.F && _isCmdr) {
				if (parseInt(o.maxage) >= 200)
					bonus('fire magic', 'maxage', mult(o.F, -5));
				else if (parseInt(o.maxage) >= 50)
					bonus('fire magic', 'maxage', mult(o.F, -2));
				else if (parseInt(o.maxage))
					bonus('fire magic', 'maxage', mult(o.F, -1));
			}
		}
		//a guess...
		if (o.startage == '-1') startage = 0;
		
		//older
		if (is(o.older)) o.startage = sum(o.startage, o.older);
		
		//magic boost
		var mboosts = [];
		if (is(o.magicboost_all)) {
			mboosts.push( Format.Signed(o.magicboost_all) + ' (all paths)' );
		}
		for (var i=0, k; k= modconstants.pathkeys[i]; i++) {
			var b = o['magicboost_'+k];
			if (b) mboosts.push( Format.Signed(b) + k );
		}
		if (mboosts.length)
			o['magicboost'] = mboosts.join(' ');
		
		
		//gemprod
		var gprods = [];
		for (var i=0, k; k= modconstants.gemkeys[i]; i++) {
			var b = o['gemprod_'+k];
			if (is(b)) gprods.push( b + k );
		}
		if (gprods.length)
			o['gemprod'] = '+'+gprods.join(' +');

		
		//magic pathcost bonuses
		var n;
		var isldr = is(o.leader);
		if (n= parseInt(o.A)) {
			if (isldr) bonus('air magic', 'magicleader', n*5);
			if (_isCmdr) bonus('air magic','prec', n);
		}
		if (n= parseInt(o.B)) {
			if (isldr) bonus('blood magic', 'magicleader', n*5);
			if (isldr) bonus('blood magic', 'undeadleader', n*5);
		}
		if (n= parseInt(o.D)) {
			//if (isldr) bonus('death magic', 'magicleader', n*5);
			if (isldr) bonus('death magic', 'undeadleader', n*30);
			if (o.fear && _isCmdr)
				bonus('death magic', 'fear', n);						
			else if (n >= 5 && _isCmdr)
				bonus('death magic', 'fear', n-5);
			
		}
		if (n= parseInt(o.S)) {
			if (isldr) bonus('astral magic', 'magicleader', n*10);
		}
		if (n= parseInt(o.E)) {
			if (isldr) bonus('earth magic', 'magicleader', n*5);
			if (_isCmdr) bonus('earth magic', 'prot', n);
		}
		if (n= parseInt(o.F)) {
			if (isldr) bonus('fire magic', 'leader', n*5);  
			//if (isldr) bonus('fire magic', 'leader', n*5);
			if (isldr) bonus('fire magic', 'magicleader', n*5);
			if (_isCmdr) bonus('fire magic', 'att', n);
			
			if (is(o.fireshield) && _isCmdr && n>1)
				bonus('fire magic', 'fireshield', n-1);

			if (is(o.heat) && _isCmdr)
				bonus('fire magic', 'heat', n);
		}
		if (n= parseInt(o.N)) {
			if (isldr) bonus('nature magic', 'magicleader', n*5);
			if (_isCmdr) bonus('nature magic', 'supplybonus', n*10);
		}
		if (n= parseInt(o.W)) {	
			if (isldr) bonus('water magic', 'magicleader', n*5);
			if (_isCmdr) bonus('water magic', 'def', n);
			
			if (is(o.cold) && _isCmdr)
				bonus('water magic', 'cold', n);
		}				
		
		//mounted lose feet slots
		if (is(o.mounted))
			o.foot = '0'
		
		//item slots
		var slotwords = [];
		var slotorder = ['hand','hands',  'head','heads',  'body','bodies',  'foot','feet',  'misc','misc'];
		for (var j=0; j<slotorder.length; j+=2) {
			var t = slotorder[j];
			var n = parseInt(o[t]);
			if (!n)
				continue;
			if (n > 1)
				t = String(n) + ' '+ slotorder[j+1];
			if (t == 'foot')
				t = 'feet';
			slotwords.push(t);
		}			
		if (slotwords.length)
			o.slots = slotwords.join(', ');
		
		//old age
		var oldyears = parseInt(o.startage) - parseInt(o.maxage);
		if (oldyears >= 0) {
			o.isold = '1';
			
			var oldmult = 1 + Math.floor(5 * oldyears / parseInt(o.maxage));
			bonus('old age', 'str', -1 * oldmult);
			bonus('old age', 'att', -1 * oldmult);
			bonus('old age', 'def', -1 * oldmult);
			bonus('old age', 'prec', -0.5 * oldmult);
			bonus('old age', 'enc',  1 * oldmult);
			bonus('old age', 'hp', mult(o.hp, -0.05 * oldmult));
			bonus('old age', 'ap', mult(o.ap, -0.05 * oldmult));
		}
		//afflictions
		if (o.afflicted) {
			var affo = DMI.MAffliction.lookup[ o.afflicted ]
			if (affo) affo.afflictUnit(o, bonus);
		}
		
		//mounted def bonus
		if (is(o.mounted))
			bonus('mounted', 'def', 3);
				
		//weapons
		var def_wpns = 0;
		for (var i=0, w;  w= o.weapons[i];  i++) 
			def_wpns += parseInt(w.def || '0');
		bonus('weapons', 'def', def_wpns);
			
		//multi weapon penalty
		var countarms = 0;
		var mwpnpen = 0;
		for (var i=0, w;  w= o.weapons[i]; i++) {
			if (w.wpnclass == 'melee' && !w.bonus && is(o.hand)) {
				countarms++;
				mwpnpen -= parseInt(w.len || '0');
			}
		}
		if (countarms > 1 && mwpnpen < 0 && is(o.ambidextrous)) {
			//ambidextrous
			//var ambidextrous = parseInt(o.ambidextrous || '0');
			// if (ambidextrous > -mwpnpen) ambidextrous = -mwpnpen;
				
			// bonus('dual wield', 'att', mwpnpen);
			// bonus('ambidextrous', 'att', ambidextrous);
			
			mwpnpen = Math.min(0, mwpnpen + parseInt(o.ambidextrous));
			o.titles.att = o.titles.att  ?  o.titles.att + '\n('+'dual wield '+mwpnpen+')' : '';
		}
		
		//wpn att / prec tooltips
		for (var i=0, w;  w= o.weapons[i]; i++) {
			if (w.wpnclass == 'melee') {
				// o.titles.att = o.titles.att  ?  o.titles.att+', \n'  :  '';
				// o.titles.att += ' '+w.name+'  ->  '+ sum(o.att, w.att);
				
				o.titles.att = o.titles.att  ?  o.titles.att+', \n'  :  '';
				if (countarms > 1 && mwpnpen && !w.bonus)
					o.titles.att += ' '+w.name+'  ->  '+ sum(sum(o.att, w.att), mwpnpen);
				else
					o.titles.att += ' '+w.name+'  ->  '+ sum(o.att, w.att);
				
			} else {
				o.titles.prec = o.titles.prec  ?  o.titles.prec+', \n'  :  '';
				o.titles.prec += ' '+w.name+'  ->  '+ sum(o.prec, w.prec);
			}
		}
		
		//protection & encumbrance from armor
		var p_nat = parseInt(o.prot || '0');
		
		var p_body = 0, p_head = 0;
		var def_armor = 0, enc_armor = 0;
		var def_parry = 0;
		for (var i=0, a; a= o.armor[i]; i++) {
			enc_armor += parseInt(a.enc || '0');
			def_armor += parseInt(a.def || '0');
			
			if (a.type == 'armor') 
				p_body = parseInt(a.protbody);

			else if (a.type == 'helm')
				p_head = parseInt(a.prothead);
			
			else if (a.type == 'shield')
				def_parry = a.parry;
			
			else if (a.type == 'misc') { 
				//use misc armor prot instead of basic?                                                
				p_inc = parseInt(a.prot || '0') - p_nat;
				if (p_inc > 0) {
					p_nat += p_inc;
					bonus(a.name, 'prot', p_inc);
				}
			}
		}
		bonus('armor', 'def', def_armor);
		bonus('shield parry', 'def', def_parry);
		
		if (p_body || p_head) {
			//displayed values
			p_body = (p_nat + p_body - (p_nat * p_body/40));
			p_head = (p_nat + p_head - (p_nat * p_head/40));
			var p_total = ((p_body * 4) + p_head) / 5;
			
			// p_total = p_total > 10  ?  math.floor(p_total)  :  

			//display strings			
			o.prot = String(Math.round(p_total));
			o.titles.prot = 'basic '+(o.titles.prot || String(p_nat));
			o.titles.prot += ',  head '+Math.round(p_head)+',  body '+Math.round(p_body);
		}		
		//armor encumbrance 
		if (enc_armor) {
			//casting encumbrance (double armor)
			o.casting_enc = parseInt(o.enc) + (enc_armor*2);
			
			//mounted ignore armor
			if (!is(o.mounted)) {
				//for enc 0 (undead) armor only affects speed
				bonus('armor', 'ap', -enc_armor);
				if (o.enc!='0')
					bonus('armor', 'enc', enc_armor);
			}
			//is caster?
			if (o.magicskill) {
				o.titles.enc = o.titles.enc ? o.titles.enc+',  \n' : ''; 
				o.titles.enc += 'spellcasting encumbrance: '+o.casting_enc;
			}
		}
		
		//bonus( 'enc', 'ap', negative(o.enc));
		
		//minimum ap
		if (parseInt(o.ap || '0') < 2)
			o.ap = '2';
			
		//flight move bonus
		if (parseInt(o.mapmove || '0') < 3 && is(o.flying))
			bonus('flying', 'mapmove', 1);
		
		//oneeye as flag		
		if (o.eyes == '1')
			o.oneeye = '1'
		if (o.eyes == '0')
			o.noeyes = '1'
		
		//stormpower implies stormimmunity	
		if (is(o.stormpower))
			o.stormimmune = '1';
		
		//displayed values 1 less than specified (in dbase and mod commands)
		if (is(o.awe))		o.awe = 	sum(o.awe, -1);
		if (is(o.animalawe))	o.animalawe = 	sum(o.animalawe, -1);
		if (is(o.sacredawe))	o.sacredawe = 	sum(o.sacredawe, -1);
		if (is(o.bloodvengeance))o.bloodvengeance = sum(o.bloodvengeance, -1);
		
		//morale 50+ implies mindless
		if (parseInt(o.mor) >= 50) o.mind = '1';
	}
}


//////////////////////////////////////////////////////////////////////////
// DEFINE GRID
//////////////////////////////////////////////////////////////////////////

 function formatGold(_,__,v){ return v || ''; }
 function formatRes(_,__,v){ return v || ''; }
 function formatType(_,__,v,___,o){ return o.type; }
 function formatHoly(_,__,v,___,o){  return v=='1' ?  Format.AbilityIcon('holy', 'sacred')  :  ''; }
 function formatNation(_,__,v,___,o){ return o.nationsummary; }

MUnit.CGrid = Utils.Class( DMI.CGrid, function() {
		
	//grid columns
	var columns = [
		{ id: "name",     width: 115, name: "Unit Name", field: "linkname", sortable: true, sortCmp: 'text' },
		{ id: "nation",   width: 60, name: "Nation", field: "nationsummary", sortable: true, formatter: formatNation, sortCmp: 'text' },
		{ id: "type",     width: 60, name: "Type", field: "sorttype", sortable: true, formatter: formatType, sortCmp: 'text' },
		{ id: "gcost",     width: 27, name: "Gold", field: "gcost", sortable: true, cssClass: "numeric", formatter: formatGold },
		{ id: "rcost",     width: 23, name: "Res", field: "rcost", sortable: true, cssClass: "numeric", formatter: formatRes },		
		{ id: "sacred",     width: 20, name: "Sacred", field: "holy", sortable: true, formatter: formatHoly },
		{ id: "listed_mpath",width: 110, name: "Magic", field: "listed_mpath", sortable: true, formatter: DMI.GridFormat.OrderedPaths, sortCmp: 'text' }
	];
	
	this.superClass.call(this, 'unit', modctx.unitdata, columns); //superconstructor
	
	$(this.domsel+' .grid-container').css('width', 705);//set table width
	
	//closure scope
	var that = this;
	
	//replace text with holy icon (synchronous call won't work in chrome)
	setTimeout(function(){
		$(that.domsel+" div.slick-header-column[title=Sacred] span.slick-column-name").replaceWith(Format.AbilityIcon('holy'));
	},50);	
	
	//selecting a nation
	$(that.domselp+" select.nation").bind('change', function(e) {
		//clicked a nation? (or era.. but not "any")
		if (! $(that.domselp+" select.nation option.default").prop('selected')) {
			//currently showing "all units"?
			if ( $(that.domselp+" select.type option.default").prop('selected')) {
				//show only national units
				$(that.domselp+" select.type option.available").prop('selected', true).parent().saveState();
				$(that.domselp+" input.national").prop('checked', true).saveState();
				$(that.domselp+".filters-units input.clear-filters-btn").show();
			}
		}
	});
	
	//selecting national/generic deselects the other
	$(that.domselp+" input.national").bind('change click', function(e) {
		if ($(this).prop('checked')) 
			$(that.domselp+" input.generic").prop('checked', false).saveState();
	});
	$(that.domselp+" input.generic").bind('change click', function(e) {
		if ($(this).prop('checked')) 
			$(that.domselp+" input.national").prop('checked', false).saveState();
	});
	
	

	//reads search boxes
	this.getSearchArgs = function(domsel) {
		var args = Utils.merge(this.getPropertyMatchArgs(), {
			str: $(that.domselp+" input.search-box").val().toLowerCase(),
			nation: $(that.domselp+" select.nation").val(),
			types: Utils.splitToLookup( $(that.domselp+" select.type").val(), ','),
			
			generic: $(that.domselp+" input.generic:checked").val(),
			national: $(that.domselp+" input.national:checked").val()
		});
		if ($.isEmptyObject(args.types)) delete args.types;
		
		//whole era
		if (args.nation == 'EA' || args.nation == 'MA' || args.nation == 'LA') {
			args.eracode = args.nation;
			delete args.nation;
		}
		else args.nation = modctx.nationlookup[ args.nation ];

		return args;
	}
	
	//apply search
	this.searchFilter =  function(o, args) {
		//type in id to ignore filters
		if (args.str && args.str == String(o.id)) return true;
		
		//search string
		if (args.str && o.searchable.indexOf(args.str) == -1)
			return false;

		//type		
		if (args.types && !args.types[o.type])
			return false;
		
		//national (national units only)
		if (args.national && !o.nations)
			return false;
		//generic (generic units only)
		if (args.generic && o.nations)
			return false;

		//era
		if (args.eracode && o.eracodes) {
			if (!o.eracodes[args.eracode])
				return false;			
		}
			
		//nation
		if (args.nation && o.nations) {
			if (!o.nations[args.nation.id])
				return false;
		}
		
		//key =~ val
		if (args.key) {
			//need to finalise stats now..
			DMI.MUnit.prepareForRender(o);
			
			var r = o.matchProperty(o, args.key, args.comp, args.val);
			if (args.not  ?  r  :  !r)
				return false;
		}
		
		if (args.customjs) {
			var res = DMI.customFilter(o, args.customjs);
			if (res == '#ERROR#')
				args.customjs = null;
			else if (!res)
				return false;
		}
		
		return true;
	}	
	
	//customise initial sort
	this.initialSortTrigger = this.domsel+" div.slick-header-column[title=Type]";

	//customise sort	
	this.preSort = function(){
		//bound scope
		var boosterSortPriority = ['F', 'A', 'W', 'E', 'S', 'D', 'N', 'B', 'H', 'U', 'R'];
		var isSortedOnBoosters = false;
		var data = modctx.unitdata;
			
		//the actual callback
		return function(e, args) {
			if (args.sortCol.field == 'listed_mpath') { //boosters == paths
				if (isSortedOnBoosters) {
					//rotate priority 
					var pL = boosterSortPriority[0];
					boosterSortPriority.push(boosterSortPriority.shift());
					
					//push last priority to end
					var regex = new RegExp('^.('+pL+'\\d+ )(.*)$');
					//console.log(regex);
					for (var i=0; i<data.length; i++) {
						var b = data[i].listed_mpath;
						if (b && b.indexOf(pL)!=-1)
							data[i].listed_mpath = b.replace(regex, "0$2$1");
					}
				}
				var L = boosterSortPriority[0];
				
				//set first character to number
				for (var i=0; i<data.length; i++) {
					var b = data[i].listed_mpath;
					if (b && b.indexOf(L)!=-1)
						data[i].listed_mpath =  String.fromCharCode(100+parseInt(b.substr(2,2))) + b.substr(1);
				}
				//switch sort column header icon
				if ( $('#unitboosterordericon')
				     .attr({alt:L, src:'images/magicicons/Path_'+L+'.png', 'class':'pathicon Path_'+L})
				     .css('visibility','visible')
				     .length==0 ) 
				{
					//add icon if not exists yet
					$(".slick-header-column[title=Magic]")
					.append('<img id="unitboosterordericon" alt="'+L+'" class="pathicon Path_'+L+'" src="images/magicicons/Path_'+L+'.png" />')
					.find(".slick-sort-indicator").css('visibility','hidden');
				}
				//fix sort direction
				args.sortAsc = false;
				isSortedOnBoosters = true;
			} 
			else  {
				//hide sort column header icon if sorting another column
				$('#unitboosterordericon').css('visibility','hidden');
				isSortedOnBoosters = false;
			}
		}
		//exit bound scope
	}();
	
	this.defaultSortCmp = function(r1,r2) {
		return (r2.gcost - r1.gcost) || (r2.rcost - r1.rcost);
	}	

	//mouseover events for random magic panes	
	$(that.domsel+' .grid-container').attachRefMouseEvents();
	
	//call filters and update  display
	//asyncronous to make sure all filter inputs are correctly initialised  
	setTimeout(function() { 
		that.init(); 
	},0);
});
MUnit.matchProperty = DMI.matchProperty;



function chainedUnitRef(o, key, refq) {
	var ref = '<span style="white-space:nowrap;">'+Utils.unitRef(parseInt(o.id), o.linkname)+'</span>';
	//var ref = Utils.unitRef(parseInt(o.id))+'</span>';
	//is already in queue?
	if (Utils.inArray(ref, refq)) {
		if (ref == refq[0]) {
			if (refq.length == 2)
				return '⇔&nbsp;'+refq[1];
			else
				refq.push('‹this›');
				// refq.push('«this»');
				// refq.push('&lt;this&gt;');			
		}
		else {
			var i=0; while (refq[i]!=ref) i++;
			refq.push('(repeat from '+i+')');
		}
	}
	else {
		refq.push(ref);
		var nextu;
		if (o[key] && (nextu= modctx.unitlookup[o[key]]))
			return chainedUnitRef(nextu, key, refq);
	}
	var n= 1;
	return '⇒&nbsp;'+refq.slice(1).join(' ⇒&nbsp;');
}
function twinUnitRef(o, key, return_key) {
	var shape2 = modctx.unitlookup[ o[key] ];
	if (shape2) {
		var ref = '<span style="white-space:nowrap;">'+Utils.unitRef(parseInt(shape2.id), shape2.linkname)+'</span>'
		if (shape2[return_key] && o == modctx.unitlookup[ shape2[return_key] ]) {
			return  '⇔&nbsp;'+ref;
		}
		else
			return  '⇒&nbsp;'+ref;
	}
	return '';
}

//////////////////////////////////////////////////////////////////////////
// OVERLAY RENDERING
//////////////////////////////////////////////////////////////////////////

var aliases = {};
var formats = {};
var displayorder = Utils.cutDisplayOrder(aliases, formats,
[
	//	dbase key	displayed key		function/dict to format value
	'hp',	'hit points',	function(v,o){ return v + ' &nbsp;(size '+o.size+')'; },
	'prot',	'protection',	{'0':'0 '},
	'mor',	'morale',	{'0':'0 '},
	'mr',	'magic res',	{'0':'0 '},
	'enc',	'encumbrance',	{'0':'0 '},
	
	'str',	'strength',	{'0':'0 '},
	'att',	'attack skill',	{'0':'0 '},
	'def',	'defence skill',{'0':'0 '},
	'prec',	'precision',	{'0':'0 '},
	'ap', 	'move',		function(v,o){ return o.mapmove + ' / '+o.ap; }
]);
var displayorder_cmdr = Utils.cutDisplayOrder(aliases, formats,
[
	'leader', 	'leadership', function(v,o){ 
		var ldr_arr = [];
		if (Utils.is(o.leader)) ldr_arr.push(o.leader);
		if (Utils.is(o.undeadleader)) ldr_arr.push('('+o.undeadleader+' undead<span class="internal-inline"> [undeadleader]</span>)');
		if (Utils.is(o.magicleader)) ldr_arr.push('('+o.magicleader+' magic<span class="internal-inline"> [magicleader]</span>)');
		return ldr_arr.join(' + ');
	},
	//return o.ldr_str; },
	'magicskill',	'magic paths',	function(v,o){
		return Format.Paths(v.replace(/U\d*/, function(s){return Utils.rndMagicRef(Math.floor(o.id), s);}));
	},
	'magicboost',	'magic boost', Format.Paths, 
	'slots',	'item slots'
]);
var displayorder_pret = Utils.cutDisplayOrder(aliases, formats,
[
	'startdom',		'base dominion',
	'pathcost',		'new path cost'
]);
var displayorder2 = Utils.cutDisplayOrder(aliases, formats,
[
	'maxage',	'age',	function(v,o){ return o.startage + ' ('+v+')'; },
	
	'gemprod',	'generates',	function(v){ return v!='0' && Format.PerTurn(Format.Gems(v)); },
	// 'gemprod_A',	'generates',	function(v){ return v!='0' && Format.PerTurn(Format.Gems(v+'A')); },
	// 'gemprod_B',	'generates',	function(v){ return v!='0' && Format.PerTurn(Format.Gems(v+'B')); },
	// 'gemprod_D',	'generates',	function(v){ return v!='0' && Format.PerTurn(Format.Gems(v+'D')); },
	// 'gemprod_E',	'generates',	function(v){ return v!='0' && Format.PerTurn(Format.Gems(v+'E')); },
	// 'gemprod_F',	'generates',	function(v){ return v!='0' && Format.PerTurn(Format.Gems(v+'F')); },
	// 'gemprod_S',	'generates',	function(v){ return v!='0' && Format.PerTurn(Format.Gems(v+'S')); },
	// 'gemprod_N',	'generates',	function(v){ return v!='0' && Format.PerTurn(Format.Gems(v+'N')); },
	// 'gemprod_W',	'generates',	function(v){ return v!='0' && Format.PerTurn(Format.Gems(v+'W')); },
	
	'onebattlespell','casts each battle',		Utils.spellRef,
	
	'regeneration',	'regeneration',	Format.Percent,
	'uwregen',	'underwater regen',	Format.Percent,
	'landregen',	'land regen',	Format.Percent,

	'fireres',	'resist fire',	Format.Percent,
	'coldres',	'resist cold',	Format.Percent,
	'poisonres',	'resist poison',Format.Percent,
	'shockres',	'resist shock',	Format.Percent,
	
	'darkvision',	'dark vision',	Format.Percent,
	'stealthy',	'stealthy',	Format.SignedZero,//{0:'+0'},
	'healer',	'healer',
	
	'cold',		'cold aura',
	'heat',		'heat aura',
	'poisoncloud',	'poison cloud',
	'diseasecloud',	'disease cloud',
	'fireshield',	'fire shield',
	'banefireshield','banefire shield',
	'bloodvengeance','blood vengeance', Format.SignedZero,
	
	'ethtrue',	'true ethereal', Format.Percent,
	'horrormark',	'horrormark attackers',
	'poisonarmor',	'poison barbs',
	
	'iceprot',	'ice protection',
	'firepower',	'fire power',
	'stormpower',	'storm power',
	'coldpower',	'cold power',
	'darkpower',	'dark power',
	
	'springpower',	'spring power',
	'summerpower',	'summer power',
	'fallpower',	'fall power',
	'winterpower',	'winter power',
	
	'fear',		'fear',		Format.SignedZero,
	'awe',		'awe',		Format.SignedZero,
	'sacredawe',	'halt heretic',	Format.SignedZero,
	'animalawe',	'animal awe',	Format.SignedZero,
	
	'standard',	'standard',
	
	'ambidextrous',	'ambidextrous',
	'reinvigoration','reinvigoration',
	'berserk',	'berserker',		Format.SignedZero,
	
	'supplybonus',	'supply bonus',		Format.Signed,
	'siegebonus',	'siege bonus',		Format.Signed,
	'castledef',	'castle defence',	Format.Signed,
	'patrolbonus',	'patrol bonus',		Format.Signed,
	'pillagebonus',	'pillage bonus',	Format.Signed,
	'alchemy',	'alchemy bonus',	Format.Percent,
	'forgebonus',	'forge bonus',		Format.Percent,
	'douse',	'blood hunt bonus',	Format.Signed,
	'nobadevents',	'fortune teller',	Format.Percent,
	'goodevents',	'good events',		Format.SignedPercent,

	'spreaddom',	'spreads dominion',
	'incunrest',	'increases unrest',	Format.SignedPerTurn,
	
	'leper',	'leper',		Format.Percent,
	'popkill',	'kills population',	function(v,o){ return Format.PerTurn( parseInt(v)*10 ) },
	'homesick',	'homesick',		Format.Percent,
	'uwdamage',	'damaged underwater',	Format.Percent,

	'heretic',	'heretic',
	'shatteredsoul','shattered soul', 	Format.Percent, //tartarian
	'insane',	'insane',		Format.Percent,
	'afflicted',	'afflicted',		Utils.afflictionRef,
	
	'eyes',		'eyes',			{'0':'0', '1':'0'},
	
	'void',		'void summoning',	Format.Signed, //rl'yeh	
	'voidsanity',	'void sanity',		
	'startaff',	'starts afflicted',	Format.Percent,
	
	'uwtransport',	'underwater transport',	function(v){ return 'total size '+v },

	'seduce',	'capture cmdr (seduction)',	function(v){ if (v=='0') return '0'; return 'morale vs '+v; },
	'succubus',	'capture cmdr (succubus)',	function(v){ if (v=='0') return '0'; return 'morale vs '+v; },
	'corruption',	'capture cmdr (corruption)',	function(v){ if (v=='0') return '0'; return 'morale vs '+v; },
	'beckon',	'lure cmdr into sea',		function(v){ if (v=='0') return '0'; return 'morale vs '+v; },

	'domsummon',	'dominion attracts',	function(v,o){ return Utils.unitOfTypeRef(v, 'special') + ' x DRN'; },
	'domsummon2',	'dominion attracts',	function(v,o){ return Utils.unitOfTypeRef(v, 'special') + ' x DRN/2'; },
	'domsummon20',	'dominion attracts',	function(v,o){ return Utils.unitOfTypeRef(v, 'special') + ' x DRN/20'; },
		
	'makemonster1',	'summon allies',	function(v,o){ return '1 x '+Utils.unitOfTypeRef(v, 'special'); },
	'makemonster2',	'summon allies',	function(v,o){ return '2 x '+Utils.unitOfTypeRef(v, 'special'); },
	'makemonster3',	'summon allies',	function(v,o){ return '3 x '+Utils.unitOfTypeRef(v, 'special'); },
	'makemonster4',	'summon allies',	function(v,o){ return '4 x '+Utils.unitOfTypeRef(v, 'special'); },
	'makemonster5',	'summon allies',	function(v,o){ return '5 x '+Utils.unitOfTypeRef(v, 'special'); },
	
	'summon1',	'auto summons',	function(v,o){ return Format.PerTurn( '1 x '+Utils.unitOfTypeRef(v, 'special') ); },
	'summon2',	'auto summons',	function(v,o){ return Format.PerTurn( '2 x '+Utils.unitOfTypeRef(v, 'special') ); },
	'summon3',	'auto summons',	function(v,o){ return Format.PerTurn( '3 x '+Utils.unitOfTypeRef(v, 'special') ); },
	'summon4',	'auto summons',	function(v,o){ return Format.PerTurn( '4 x '+Utils.unitOfTypeRef(v, 'special') ); },
	'summon5',	'auto summons',	function(v,o){ return Format.PerTurn( '5 x '+Utils.unitOfTypeRef(v, 'special') ); }
]);		
var displayorder_long = Utils.cutDisplayOrder(aliases, formats,
[
	//############LONG?
	'ivylord',	'ivy lord',		function(v){ return '+'+v+' '+Utils.unitRef(361)+' / '+Utils.unitRef(362)+' awakening'; },
	'lamialord',	'lamia lord',		function(v){ return '+'+v+' '+Utils.unitRef(394)+' summoning'; },

	'firstshape',	'natural shape',function(v,o){	return chainedUnitRef(o, 'firstshape', []);	},
	'secondshape',	'wounded shape',function(v,o){	return chainedUnitRef(o, 'secondshape', []);	},
	'shapechange',	'shape changer',function(v,o){	return chainedUnitRef(o, 'shapechange', []);	},
	'secondtmpshape','dying shape',	function(v,o){	return chainedUnitRef(o, 'secondtmpshape', []);	},
	'landshape',	'land shape',	function(v,o){	return twinUnitRef(o, 'landshape', 'watershape');	},
	'watershape',	'sea shape',	function(v,o){	return twinUnitRef(o, 'watershape', 'landshape');	},
	'forestshape',	'forest shape',	function(v,o){	return twinUnitRef(o, 'forestshape', 'plainshape');	},
	'plainshape',	'normal shape',	function(v,o){	return twinUnitRef(o, 'plainshape', 'forestshape');	},
	'prophetshape',	'prophet shape',function(v,o){	return chainedUnitRef(o, 'prophetshape', []);	},
	
	'special',		'special',	function(v){ return Utils.parseObjectRefs(v.replace(/;/g,'<br />')); }
]);
var flagorder = Utils.cutDisplayOrder(aliases, formats,
[
//	dbase key	displayed key		function/dict to format value
	'holy',		'sacred',
	'unique',	'unique',
	'immortal',	'immortal',
	'isold',	'old age',
	'attacksfriends', 'attacks friends',
	'phantasmal'	, 'phantasmal being',
	
	'sailing',	'sailor',
	'mounted',	'mounted',                                                         
	'immobile',	'immobile',
	
	'animal',	'animal',
	'undead',	'undead',
	'demon',	'demon',
	'magicbeing',	'magic being',
	'inanimate',	'inanimate',
	'mind',		'mindless',
	'ethereal',	'ethereal',
	'illusion',	'glamour',	
	'flying',	'flying',
	'stormimmune',	'storm flyer',
	
	'trample',	'trample',
	'blind',	'blind fighter',
	'heal',		'recuperation',
	'spy',		'spy',
	'assassin',	'assassin',
	'drainimmune',	'ignores drain scales',
		
	'coldblood',	'cold blooded',
	'pooramphibian','poor amphibian',
	'amphibian',	'amphibious',
	'aquatic',	'aquatic',
	'neednoteat',	'need not eat',
	'noheal',	'only heals in lab',
	
	'wastesurvival',	'wasteland survival',
	'mountainsurvival',	'mountain survival',
	'swampsurvival',	'swamp survival',
	'forestsurvival',	'forest survival',
	
	'female',	'female',
	'seductionimmune','immune to seduction',
	'stonebeing',	'stone being',
	'plague',	'spreads plague',
	
	'petrify',	'petrifies attackers',
	'eyeloss',	'eyeloss on attackers',
	'entangle',	'entangles attackers',
	
	'inquisitor',	'inquisitor',
	
	'oneeye',	'one eyed',
	'noeyes',	'eyeless',
	'reincarnation','reincarnation'	
]);
var hiddenkeys = Utils.cutDisplayOrder(aliases, formats,
[
	'id', 		'unit #',	function(v){ return Math.floor(v); }
]);
var modderkeys = Utils.cutDisplayOrder(aliases, formats,
[
	'ressize',	'ressize',
	'nametype',	'nametype',
	'speciallook',	'speciallook',
	//'dupes',	'duplicates',	function(v,o){ return v.length; },
	
	'sprite',	'sprite', function(v,o){ return v && v.url; },
	'unknown',	'unknown effect',
	'notes',	'notes'
]);
var ignorekeys = {
	SRCstartage:1, SRCmaxage:1,
	nonmagicresearcher:1,

	
	gemprod_A:1, gemprod_D:1, gemprod_E:1, gemprod_F:1, gemprod_S:1, gemprod_N:1, gemprod_W:1, gemprod_B:1, 
	
	modded:1, moddedname:1,
	dupes:1,
	sorttype:1,	
	
	titles:1, fullname:1, subname:1, linkname:1,
	size:1, uniquename:1,
	
	ldr_str:1, // leader:1,	
	magicleader:1,
	undeadleader:1,
	
	mapmove:1,
	startage:1, older:1,
	casting_enc:1,	
	
	searchable:1,
	
	researchbonus:1, listed_mpath:1, 
	n_makemonster:1, n_summon:1,	
	
	hand:1, head:1, body:1, foot:1, misc:1, 
	

	A:1, B:1, D:1, E:1, F:1, N:1, S:1, W:1, H:1, randompaths:1,
	magicboost_A:1, magicboost_B:1, magicboost_D:1, magicboost_E:1, magicboost_F:1, magicboost_N:1, magicboost_S:1, magicboost_W:1, magicboost_H:1,
	magicboost_all:1,
	
	gcost:1, rcost:1,
	
	weapons:1, armor:1, helmet:1, shield:1, wpn1:1, wpn2:1, wpn3:1, wpn4:1, wpn5:1, wpn6:1,
	
	eracodes:1, nations:1, numnations:1, nationsummary:1, type:1,
	summonedby:1, createdby:1,

	//common fields
	name:1, descr:1,
	searchable:1, renderOverlay:1, matchProperty:1
};	

MUnit.renderOverlay = function(o, isPopup) {
	MUnit.prepareForRender(o);		
	var descrpath = 'gamedata/unitdescr/';
	
	//template
	var h=''
	h+='<div class="unit overlay-contents">';
	
	// //header
	h+='	<div class="overlay-header" title="unit #:'+o.id+'"> ';
	h+=' 		<input class="overlay-pin" type="image" src="images/PinPageTrns.png" title="unpin" />';

	h+='		<h2>'+o.fullname+'</h2> ';
	
	//nation/commander info
	var nref = DMI.MNation.nationUnitRefs(o.nations);
	if (nref || o.type || o.holy) {
		h+='	<p style="float:right; clear:right;">&nbsp;'+(o.holy ? Format.AbilityIcon('holy', 'sacred') : '')+'</p> ';
		h+='	<p style="float:right;">'+(o.type ? o.type : '')+'</p> ';
		h+='	<p>'+(nref || '&nbsp;')+'</p> ';
	}
	h+='	</div>';

	//body
	h+='	<div class="overlay-main" style="clear:both;">';
	h+='	<img style="float:right; clear:right;" src="'+o.sprite.url+'" />';
	h+='	<div style="float:right; clear:right; max-width:50%;">';
	var tags = [];
	for (var i=0; i<o.weapons.length; i++)
		tags.push(Utils.wpnRef(o.weapons[i].id));
	if (tags.length)
		h+='	<p>Weapons<span class="internal-inline"> [weapon]</span>:<br />'+ tags.join('<br /> ') +'</p>';		
	
	var tags = [];
	for (var i=0; i<o.armor.length; i++)
		tags.push(Utils.armorRef(o.armor[i].id));
	if (tags.length)
		h+='	<p>Armor<span class="internal-inline"> [armor]</span>:<br />'+ tags.join('<br /> ') +'</p>';
	h+='	</div>';	
	
	
	h+='		<table class="overlay-table"> ';
	h+= 			Utils.renderDetailsRows(o, hiddenkeys, aliases, formats, 'hidden-row');
	h+= 			Utils.renderDetailsRows(o, displayorder, aliases, formats);
	h+=' 		</table> ';
	
	h+='		<table class="overlay-table" style="margin-bottom:0px"> ';
	h+= 			Utils.renderDetailsRows(o, displayorder2, aliases, formats);
	h+=' 		</table> ';
	h+='		<table class="overlay-table" style="margin-top:0px"> ';
	h+= 			Utils.renderDetailsRows(o, displayorder_long, aliases, formats);
	h+= 			Utils.renderStrangeDetailsRows(o, ignorekeys, aliases, 'strange');
	h+=' 		</table> ';
	
	var flagrows =		Utils.renderDetailsFlags(o, flagorder, aliases, formats);
	if (flagrows) h+='<p style="margin-top:0px;padding-top:0px;"> '+flagrows+'</p>';
	
	
	//commander details
	h+='		<table class="overlay-table commander '+(isCmdr(o) ? '' : 'hidden-block')+'" style="clear:both;"> ';
	h+= 			Utils.renderDetailsRows(o, displayorder_cmdr, aliases, formats);
	h+= 			Utils.renderDetailsRows(o, modderkeys, aliases, formats, 'modding-row');
	h+='		</table> ';
	
	//modded
	if (o.modded) {
		h+='	<div class="modded hidden-block">' + Utils.renderModded(o) +'</div>';
	}
	
	//footer
	h+='	</div>';
	h+='	<div class="overlay-footer">';
	
	//wikilink
	if (!o.moddedname)
		h+='	<div class="overlay-wiki-link non-content">' + Utils.wikiLink(o.name, o.subname) + '</div>';
		
	//source details
	var isfree = false;
	var noupkeep = false;
	if (o.summonedby && isSummon(o)) {
		for (var i=0, refarr=[], s; s= o.summonedby[i]; i++) 
			refarr.push(Utils.spellRef(s.id) +'&nbsp;<span class="tiny flag">('+s.research+')</span>'); 
		h+='	<p class="firstline">summoned with '+refarr.join(', ')+'</p>';
		isfree = true;
	}
	else if (o.type=='pretender') {
		h+='<p class="firstline">';
		h+= ' Cost<span class="internal-inline"> [gcost]</span>: ' + (o.gcost || '0') +' pts ';
		
		h+= ' +<span class="internal-inline"> [pathcost]</span> '+ (o.pathcost || '10') + ' pts per magic path';

		h+='<br />';
		h+= ' Dominion<span class="internal-inline"> [startdom]</span>: ' + (o.startdom || '1');
		h+='</p>';
		isfree = noupkeep = true;
	}
	//else if ((!o.type) || o.type=="special") {
		// else if (o.createdby) {
		// 	for (var i=0, refarr=[], s; s= o.createdby[i]; i++) 
		// 		refarr.push(Utils.unitRef(s.id)); 
		// 	h+='	<p class="firstline">created by '+refarr.join(', ')+'</p>';
		// 	//isfree = true;
		// }
//	}	

	//cost line
	var gunit = ' gold <span class="internal-inline"> [gcost]</span>';
	var runit = ' resources <span class="internal-inline"> [rcost]</span>';
	if (isfree) {
		if (Utils.is(o.gcost) && !noupkeep)
			h+='	<p class="firstline">Upkeep: '+Math.ceil((o.holy ? 0.5 : 1) * parseInt(o.gcost) / 15) +gunit+'</p>';
	}
	else if (o.gcost == '0')
		h+='	<p class="firstline">no gold cost</p>';
	else
		h+='	<p class="firstline">costs&nbsp; '+ o.gcost+gunit +',&nbsp; '+ o.rcost+runit +'</p>';

	//created by
	if (o.createdby) {
			for (var i=0, refarr=[], s; s= o.createdby[i]; i++) 
				refarr.push(Utils.unitRef(s.id)); 
			h+='	<p style="margin:0.5em 0 0 0;" class="firstline">created by '+refarr.join(', ')+'</p>';
	}
	
	//descr
	var uid = 'c'+(new Date()).getTime();
	h+='		<div class="overlay-descr pane-extension '+uid+'"></div>';
	
	if (o.descr.text)
			Utils.insertContent( '<p>'+o.descr.text+'</p>', 'div.'+uid );
	else {
			var url = descrpath + Utils.paddedNum(Math.floor(o.descr.unitid),4)+'.txt';
			Utils.loadContent( url, 'div.'+uid );
	}	
	
	h+='	</div> ';
	h+='</div> ';
	return h;	
}


MUnit.renderRandomMagic = function(o) {
	//template
	var h=''
	h+='<div class="random-magic overlay-contents"> ';
	
	//header
	h+='	<div class="overlay-header"> ';
	h+='		<h2>'+o.name+' - random magic</h2>';
	h+='	</div>';
	
	//body
	h+='	<div class="overlay-main">';
	h+=' 		<input class="overlay-pin" type="image" src="images/PinPageTrns.png" title="unpin" />';
	
	h+=' 		<div class="hidden-block">unit #: '+o.id+'</div>';
	
	h+='		<table class="random-magic">';
	h+='			<tr class="header-row">';
	h+='				<th>possible paths</th>';
	h+='				<th>level</th>';
	h+='				<th>chance</th>';
	h+='			</tr>';
	
	for (var i=0, r; r= o.randompaths[i]; i++) {
		h+='		<tr>';
		h+='			<td> '+Format.Paths(r.paths)+' </td>';
		h+='			<td> +'+r.levels+' </td>';
		h+='			<td> '+r.chance+'% </td>';
		h+='		</tr>';
	}		
	h+='		</table>';
	h+='	</div>';
	h+='</div>';
	return h;		
}


Utils.dumpCSV = function(keys, objects) {
	var str = '';
	
	//header
	for (var i=0, k; k= keys[i]; i++)
		str += k+'\t';
	str += '\n';
	
	for (var oi=0, o;  o= objects[oi];  oi++) {
		//object
		for (var i=0, k; k= keys[i]; i++) {
			//format str/num
			if (o[k] && (typeof(o[k])=='string'  ||  typeof(o[k])=='number'))
				str += String(o[k]).replace(/\\[trn]/ig,' ')+'\t';
			//obj.id
			else if (o[k] && typeof(o[k])=='object' && o[k].id)
				str += o[k].id+'\t';
			//?
			else
				str += '\t';
		}
		str += '\n';
	}
	return str;
}
MUnit.dumpCSV = function( showkeys ) {
	for (var oi=0, o;  o= modctx.unitdata[oi];  oi++) {
		MUnit.prepareForRender(o);
	}
	
	if (!showkeys) {
		showkeys = ['id', 'name'].concat(displayorder, displayorder_cmdr, displayorder_pret, displayorder2, flagorder, Utils.objectKeys(ignorekeys));
		Utils.weedArray('modded', showkeys);
		Utils.weedArray('descr', showkeys);
		Utils.weedArray('[object Object]', showkeys);
	}
	return Utils.dumpCSV(showkeys, modctx.unitdata);
}

//namespace args
}( window.DMI = window.DMI || {}, jQuery ));
